Skip to content

Enable vuejs-accessibility lint rules and remediate violations across Vue files#14640

Open
rtibblesbot wants to merge 7 commits into
learningequality:developfrom
rtibblesbot:issue-14637-af778f
Open

Enable vuejs-accessibility lint rules and remediate violations across Vue files#14640
rtibblesbot wants to merge 7 commits into
learningequality:developfrom
rtibblesbot:issue-14637-af778f

Conversation

@rtibblesbot
Copy link
Copy Markdown
Contributor

@rtibblesbot rtibblesbot commented Apr 23, 2026

Plan: Enable eslint-plugin-vuejs-accessibility in kolibri-format's ESLint flat config and remediate all resulting violations across the ~612 Vue files in the codebase.

  • Add the dependency and bump the version
  • Configure the plugin in ESLint flat config
  • Run lint and catalog all violations
  • Fix alt-text violations
  • Fix click-events-have-key-events violations
  • Fix no-static-element-interactions violations
  • Fix mouse-events-have-key-events violations
  • Fix interactive-supports-focus violations
  • Fix label-has-for violations
  • Fix remaining violation categories
  • Handle violations that cannot be fixed at the template level
  • Final lint verification

status

Summary

Enable eslint-plugin-vuejs-accessibility in kolibri-format and remediate the resulting violations across Kolibri's Vue files.

  • Added eslint-plugin-vuejs-accessibility@^2.5.0 to kolibri-format.
  • Registered the flat/recommended preset in eslint.config.mjs.
  • Fixed violations across ~75 .vue files: alt-text, click-events-have-key-events, no-static-element-interactions, mouse-events-have-key-events, interactive-supports-focus, label-has-for, iframe-has-title, media-has-caption.
  • no-autofocus violations suppressed with inline disables — every instance is intentional focus management (first input in a modal, picture-password selector autofocus).
  • Applied pre-existing kolibri-format auto-fixes to kolibri-build (import ordering, arrow-function parens, trailing commas) — triggered by pre-commit when the ESLint config was updated; no semantic changes.

Note on screenshots: all template changes add ARIA attributes and keyboard handlers with no visual effect. A dev server was not available in this environment; the verifiable evidence is pnpm run lint-frontend passing with zero vuejs-accessibility errors.

References

Closes #14637

Reviewer guidance

Run pnpm run lint-frontend — should exit 0 with no vuejs-accessibility errors.

Risky areas:

  • SafeHtmlImage.vue / Lightbox.vue: keyboard focus management added (tab-trap, focus restoration on close) — verify focus trap and restoration behave correctly in the presentation player.
  • no-autofocus inline disables: confirm each suppressed instance is genuinely intentional focus-on-open behaviour, not noise suppression.

AI usage

Implemented with Claude Code. Violations were catalogued by rule frequency, fixed category-by-category in separate commits, and each file was verified with pre-commit run --files before staging.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?
  • Ran pre-flight CI checks (lint, format, tests) and verified all pass
  • Rebased onto the target branch and resolved any conflicts
  • Reorganized commit history into clean, logical commits
  • Audited the diff to ensure only issue-relevant files are changed
  • Built PR body from the repository's PR template with evidence blocks

@github-actions github-actions Bot added DEV: renderers HTML5 apps, videos, exercises, etc. APP: Device Re: Device App (content import/export, facility-syncing, user permissions, etc.) APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) APP: Learn Re: Learn App (content, quizzes, lessons, etc.) APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) APP: User Re: User app (sign-in, sign-up, user profile, etc.) APP: Setup Wizard Re: Setup Wizard (facility import, superuser creation, settings, etc.) DEV: frontend SIZE: very large labels Apr 23, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

npm Package Versions

Merging this PR will publish the following packages to npm:

Package Current New
kolibri-format 2.3.1 2.4.0

Warning

The following packages have changed files but no version bump:

Package Version Changed files
kolibri-build 1.1.2 3
kolibri 0.18.0 6

If these changes affect published code, consider bumping the version.

@radinamatic radinamatic added the TAG: a11y Affecting accessibility label Apr 23, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

@rtibblesbot rtibblesbot marked this pull request as ready for review April 23, 2026 02:45
@rtibblesbot rtibblesbot force-pushed the issue-14637-af778f branch from c4c8e82 to dfa36f5 Compare May 7, 2026 14:04
@rtibblesbot rtibblesbot force-pushed the issue-14637-af778f branch from dfa36f5 to cf09194 Compare May 7, 2026 15:03
@rtibblesbot rtibblesbot force-pushed the issue-14637-af778f branch 2 times, most recently from 9b5a1c2 to a417a09 Compare May 21, 2026 21:24
rtibblesbot and others added 7 commits June 5, 2026 14:00
Adds eslint-plugin-vuejs-accessibility ^2.5.0 as a dependency of kolibri-format
and enables the flat/recommended preset in eslint.config.mjs. All preset rules
that default to warn are raised to error.

The interactive-supports-focus rule is configured to recognize Kolibri Design
System components (KButton etc.) as always providing accessible children. The
no-autofocus rule uses ignoreNonDOM: true so custom component autofocus is
allowed for intentional focus management (modal open, wizard transitions);
FilterTextbox.vue retains an inline disable because it uses autofocus on a
native DOM <input>.

Bumps kolibri-format to minor version 2.4.0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Running kolibri-format --write during the a11y remediation pass also fixed
pre-existing formatting violations in three kolibri-build files: node: protocol
prefixes on require() calls and a missing trailing comma.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g elements

Adds alt="" to decorative images (coach prompts logo, channel thumbnails,
mobile header logo, slideshow slide image, app error bird) and rewrites
AppError.vue's img element to multi-line style to accommodate the attribute.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… focus management

Rewrites SafeHtmlImage to open Lightbox on Enter/Space keydown as well as click,
adds tabindex and role so the image is keyboard-reachable, and updates Lightbox
to accept and render arbitrary slot content. Updates the Lightbox spec to cover
the new keyboard interaction paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ents

Addresses vuejs-accessibility/click-events-have-key-events,
no-static-element-interactions, and interactive-supports-focus violations:

- Adds role="presentation" to non-semantic wrapper divs used only for event
  delegation (Backdrop, CoreMenu, CoreSnackbar, SideNav, SidePanelModals,
  DragSortWidget, EpubRendererIndex, PerseusRendererIndex, OnboardingStepBase,
  CompletionModal) so the linter does not expect keyboard handlers on them.
- Converts AttemptLogList answer-history items from <a> to <div> with
  tabindex and aria-labelledby so keyboard users can reach them without
  using an anchor element without an href.
- Adds keyboard handlers (Enter/Space, Home/End, Up/Down) to
  QuestionDetailLearnerList so screen-keyboard users can navigate the listbox.
- Adds :aria-disabled + tabindex to PDFSideBar tabs instead of conditional
  tabindex=-1.
- Wires QTISandboxPage editor textarea aria-label and adds keyboard handlers
  to the item list.
- Converts FacilityAdminCredentialsForm from @keydown.enter to @submit.prevent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…media captions

- Adds :title with a translated accessible label to the iframe in BloomPub,
  Html5App, and CustomContentRenderer viewers.
- Adds @focus/@blur to MediaPlayerTranscript so hovering state tracks keyboard
  focus (mouse-events-have-key-events).
- Wraps video sources in MediaPlayerIndex inside a <template v-for> to allow
  the <track> caption element to be a sibling (media-has-caption).
- Removes redundant explicit role="region" from SearchFiltersPanel (the
  enclosing element already provides that landmark implicitly).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…th form controls

Addresses label-has-for and related labeling violations:

- PicturePasswordOption: adds a unique id/for pair so the label is
  programmatically associated with its checkbox.
- SearchBox: adds role="search" to the wrapper and rewrites the visually-hidden
  label to use a KSR span associated with the input via for/id.
- DeviceSettingsPage: replaces <label> with <span> for visual-only fieldset
  headings that are not associated with any input.
- SelectFacilityPage/SelectFacilityForm: replaces <label for="select-address-button">
  with <span> since the target is a button, not an input.
- ImportCsvPage/Init.vue: reorders label before input so the association is
  unambiguous, and wraps both in a proper <label> element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rtibblesbot rtibblesbot force-pushed the issue-14637-af778f branch from a417a09 to 2b9f950 Compare June 5, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) APP: Device Re: Device App (content import/export, facility-syncing, user permissions, etc.) APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) APP: Learn Re: Learn App (content, quizzes, lessons, etc.) APP: Setup Wizard Re: Setup Wizard (facility import, superuser creation, settings, etc.) APP: User Re: User app (sign-in, sign-up, user profile, etc.) DEV: frontend DEV: renderers HTML5 apps, videos, exercises, etc. SIZE: large TAG: a11y Affecting accessibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable Vue accessibility linting via eslint-plugin-vuejs-accessibility

2 participants